package evl

import (
	"fmt"

	"gitcode.com/deyiyangyang/showen-lang/typ"
)

// Eval evaluates a Scheme expression
func Eval(expr typ.Object, env *typ.Environment) typ.Object {
	// Handle differently based on the type of the expression
	switch expr := expr.(type) {

	// Self-evaluating expressions
	case *typ.Boolean, *typ.Integer, *typ.Rational, *typ.Real, *typ.String, *typ.Null:
		return expr

	// Symbol, look up its value in the environment
	case *typ.Symbol:
		val, err := env.Get(expr.Value)
		if err != nil {
			return &typ.String{Value: fmt.Sprintf("Error: %s", err.Error())}
		}
		return val

	// List expression
	case *typ.Pair:
		// Handle special forms
		if car, ok := expr.Car.(*typ.Symbol); ok {
			// Check if it's a special form
			symbolName := car.Value

			switch symbolName {
			case "quote":
				return evalQuote(expr, env)
			case "if":
				return evalIf(expr, env)
			case "define":
				return evalDefine(expr, env)
			case "set!":
				return evalSet(expr, env)
			case "lambda":
				return evalLambda(expr, env)
			case "begin":
				return evalBegin(expr, env)
			case "cond":
				return evalCond(expr, env)
			case "let":
				return evalLet(expr, env)
			case "load":
				return evalLoad(expr, env)
			default:
				// Regular function call
				return applyFunction(expr, env)
			}
		} else {
			// Regular function call
			return applyFunction(expr, env)
		}

	default:
		return &typ.String{Value: fmt.Sprintf("Error: unsupported expression type: %v", expr.Type())}
	}
}

// evalQuote handles the quote special form
func evalQuote(expr *typ.Pair, env *typ.Environment) typ.Object {
	// (quote expr) returns expr itself without evaluation
	// Ensure correct list length
	if !isProperList(expr) || listLength(expr) != 2 {
		return &typ.String{Value: "Error: quote requires exactly one argument"}
	}
	return expr.Cdr.(*typ.Pair).Car
}

// evalIf handles the if special form
func evalIf(expr *typ.Pair, env *typ.Environment) typ.Object {
	// (if condition consequent alternative)
	// Ensure correct list length
	if !isProperList(expr) || (listLength(expr) != 3 && listLength(expr) != 4) {
		return &typ.String{Value: "Error: if requires 2 or 3 arguments"}
	}

	// Parse list elements
	cdr := expr.Cdr.(*typ.Pair)
	condition := cdr.Car
	consequent := cdr.Cdr.(*typ.Pair).Car
	var alternative typ.Object = typ.Nil

	if listLength(expr) == 4 {
		alternative = cdr.Cdr.(*typ.Pair).Cdr.(*typ.Pair).Car
	}

	// Evaluate the condition expression
	condResult := Eval(condition, env)

	// Choose the branch to execute based on the condition result
	if !isFalse(condResult) {
		return Eval(consequent, env)
	}
	return Eval(alternative, env)
}

// evalDefine handles the define special form
func evalDefine(expr *typ.Pair, env *typ.Environment) typ.Object {
	// (define symbol value) or (define (name param1 param2 ...) body)
	if !isProperList(expr) || listLength(expr) < 3 {
		return &typ.String{Value: "Error: define requires at least two arguments"}
	}

	cdr := expr.Cdr.(*typ.Pair)
	firstArg := cdr.Car

	// Check which form of define it is
	if symbol, ok := firstArg.(*typ.Symbol); ok {
		// (define symbol value)
		value := Eval(cdr.Cdr.(*typ.Pair).Car, env)
		env.Set(symbol.Value, value)
		return symbol
	} else if pair, ok := firstArg.(*typ.Pair); ok {
		// (define (name param1 param2 ...) body)
		if name, ok := pair.Car.(*typ.Symbol); ok {
			params := pair.Cdr
			body := cdr.Cdr

			// Convert to a lambda expression
			lambdaExpr := &typ.Pair{
				Car: &typ.Symbol{Value: "lambda"},
				Cdr: &typ.Pair{
					Car: params,
					Cdr: body,
				},
			}

			// Evaluate the lambda expression
			lambda := Eval(lambdaExpr, env)

			// Define the function in the environment
			env.Set(name.Value, lambda)
			return name
		} else {
			return &typ.String{Value: "Error: invalid function name in define"}
		}
	} else {
		return &typ.String{Value: "Error: invalid define syntax"}
	}
}

// evalSet handles the set! special form
func evalSet(expr *typ.Pair, env *typ.Environment) typ.Object {
	// (set! symbol value)
	if !isProperList(expr) || listLength(expr) != 3 {
		return &typ.String{Value: "Error: set! requires exactly two arguments"}
	}

	cdr := expr.Cdr.(*typ.Pair)
	symbol, ok := cdr.Car.(*typ.Symbol)
	if !ok {
		return &typ.String{Value: "Error: first argument to set! must be a symbol"}
	}

	// Check if the variable is already defined
	_, err := env.Get(symbol.Value)
	if err != nil {
		return &typ.String{Value: fmt.Sprintf("Error: %s", err.Error())}
	}

	// Evaluate and set the new value
	value := Eval(cdr.Cdr.(*typ.Pair).Car, env)
	env.Set(symbol.Value, value)
	return value
}

// evalLambda handles the lambda special form
func evalLambda(expr *typ.Pair, env *typ.Environment) typ.Object {
	// (lambda (param1 param2 ...) body)
	if !isProperList(expr) || listLength(expr) < 3 {
		return &typ.String{Value: "Error: lambda requires at least two arguments"}
	}

	cdr := expr.Cdr.(*typ.Pair)
	params := cdr.Car
	body := cdr.Cdr

	// Check if the parameter list is a list of symbols
	if !isSymbolList(params) && params.Type() != typ.NULL {
		return &typ.String{Value: "Error: lambda parameters must be a list of symbols"}
	}

	// Create a closure
	return &typ.Closure{
		Params: params.(*typ.Pair),
		Body:   body,
		Env:    env,
	}
}

// evalBegin handles the begin special form
func evalBegin(expr *typ.Pair, env *typ.Environment) typ.Object {
	// (begin expr1 expr2 ... exprn) evaluates all expressions in order, returning the value of the last one
	if !isProperList(expr) {
		return &typ.String{Value: "Error: invalid begin syntax"}
	}

	cdr := expr.Cdr
	if cdr == typ.Nil || cdr.Type() == typ.NULL {
		return typ.Nil
	}

	// Evaluate all expressions in sequence
	var result typ.Object = typ.Nil
	current := cdr.(*typ.Pair)

	for current != nil {
		result = Eval(current.Car, env)
		if current.Cdr == typ.Nil || current.Cdr.Type() == typ.NULL {
			break
		}
		current = current.Cdr.(*typ.Pair)
	}

	return result
}

// evalCond handles the cond special form
func evalCond(expr *typ.Pair, env *typ.Environment) typ.Object {
	// (cond (test1 expr1) (test2 expr2) ... (else exprn))
	if !isProperList(expr) {
		return &typ.String{Value: "Error: invalid cond syntax"}
	}

	clauses := expr.Cdr
	if clauses == typ.Nil || clauses.Type() == typ.NULL {
		return typ.Nil
	}

	current := clauses.(*typ.Pair)

	for current != nil {
		// Each clause should be a list
		clause, ok := current.Car.(*typ.Pair)
		if !ok {
			return &typ.String{Value: "Error: cond clause must be a list"}
		}

		// Check if it's an else clause
		if test, ok := clause.Car.(*typ.Symbol); ok && test.Value == "else" {
			// else clause should be the last clause
			if current.Cdr != typ.Nil && current.Cdr.Type() != typ.NULL {
				return &typ.String{Value: "Error: else clause must be last in cond"}
			}
			// Evaluate the else clause expression
			return evalBegin(&typ.Pair{Car: &typ.Symbol{Value: "begin"}, Cdr: clause.Cdr}, env)
		}

		// Evaluate the test expression
		testResult := Eval(clause.Car, env)

		// If the test result is true, evaluate and return the expression in the clause
		if !isFalse(testResult) {
			return evalBegin(&typ.Pair{Car: &typ.Symbol{Value: "begin"}, Cdr: clause.Cdr}, env)
		}

		// Continue to the next clause
		if current.Cdr == typ.Nil || current.Cdr.Type() == typ.NULL {
			break
		}
		current = current.Cdr.(*typ.Pair)
	}

	return typ.Nil
}

// evalLet handles the let special form
func evalLet(expr *typ.Pair, env *typ.Environment) typ.Object {
	// (let ((var1 val1) (var2 val2) ...) body)
	if !isProperList(expr) || listLength(expr) < 3 {
		return &typ.String{Value: "Error: let requires at least two arguments"}
	}

	cdr := expr.Cdr.(*typ.Pair)
	bindings := cdr.Car
	body := cdr.Cdr

	// Create a new environment
	letEnv := typ.NewEnclosedEnvironment(env)

	// Process bindings
	if bindings != typ.Nil && bindings.Type() != typ.NULL {
		current := bindings.(*typ.Pair)

		for current != nil {
			// Each binding should be a list (var val)
			binding, ok := current.Car.(*typ.Pair)
			if !ok || !isProperList(binding) || listLength(binding) != 2 {
				return &typ.String{Value: "Error: invalid let binding"}
			}

			// Binding variable name should be a symbol
			varName, ok := binding.Car.(*typ.Symbol)
			if !ok {
				return &typ.String{Value: "Error: let binding name must be a symbol"}
			}

			// Evaluate the binding value (in the outer environment)
			varValue := Eval(binding.Cdr.(*typ.Pair).Car, env)

			// Set the variable in the new environment
			letEnv.Set(varName.Value, varValue)

			// Continue to the next binding
			if current.Cdr == typ.Nil || current.Cdr.Type() == typ.NULL {
				break
			}
			current = current.Cdr.(*typ.Pair)
		}
	}

	// Evaluate the body in the new environment
	return evalBegin(&typ.Pair{Car: &typ.Symbol{Value: "begin"}, Cdr: body}, letEnv)
}

// evalLoad handles the load special form (optional feature)
func evalLoad(expr *typ.Pair, env *typ.Environment) typ.Object {
	// (load "filename")
	return &typ.String{Value: "Error: load not implemented yet"}
}

// applyFunction applies a function
func applyFunction(expr *typ.Pair, env *typ.Environment) typ.Object {
	// Parse function and arguments
	function := Eval(expr.Car, env)
	args := expr.Cdr

	// Evaluate all arguments
	evalArgs := evalArguments(args, env)

	// Handle differently based on function type
	switch fn := function.(type) {

	case *typ.Builtin:
		// Call built-in function
		return fn.Fn(evalArgs)

	case *typ.Closure:
		// Call user-defined function
		return applyClosure(fn, evalArgs)

	default:
		return &typ.String{Value: fmt.Sprintf("Error: not a function: %v", function)}
	}
}

// evalArguments evaluates all arguments of a function call
func evalArguments(args typ.Object, env *typ.Environment) []typ.Object {
	var result []typ.Object

	if args == typ.Nil || args.Type() == typ.NULL {
		return result
	}

	current := args.(*typ.Pair)

	for current != nil {
		result = append(result, Eval(current.Car, env))
		if current.Cdr == typ.Nil || current.Cdr.Type() == typ.NULL {
			break
		}
		current = current.Cdr.(*typ.Pair)
	}

	return result
}

// applyClosure applies a closure (user-defined function)
func applyClosure(closure *typ.Closure, args []typ.Object) typ.Object {
	// Create a new environment that inherits the closure's environment
	fnEnv := typ.NewEnclosedEnvironment(closure.Env)

	// Bind arguments to parameters
	params := closure.Params
	var currentParam typ.Object = params

	for _, arg := range args {
		// Check if the number of arguments matches
		if currentParam == typ.Nil || currentParam.Type() == typ.NULL {
			return &typ.String{Value: "Error: too many arguments"}
		}

		// Get parameter pair
		paramPair, ok := currentParam.(*typ.Pair)
		if !ok {
			return &typ.String{Value: "Error: invalid parameter list"}
		}

		// Get parameter name
		paramName, ok := paramPair.Car.(*typ.Symbol)
		if !ok {
			return &typ.String{Value: "Error: invalid parameter name"}
		}

		// Bind argument value to parameter name
		fnEnv.Set(paramName.Value, arg)

		// Continue to the next parameter
		currentParam = paramPair.Cdr
	}

	// Check if there are unbound parameters
	if currentParam != typ.Nil && currentParam.Type() != typ.NULL {
		return &typ.String{Value: "Error: too few arguments"}
	}

	// Evaluate the function body
	return evalBegin(&typ.Pair{Car: &typ.Symbol{Value: "begin"}, Cdr: closure.Body}, fnEnv)
}

// Helper functions
func isProperList(obj typ.Object) bool {
	if obj == typ.Nil || obj.Type() == typ.NULL {
		return true
	}

	pair, ok := obj.(*typ.Pair)
	if !ok {
		return false
	}

	// Simple list check, doesn't handle circular lists
	seen := make(map[*typ.Pair]bool)
	current := pair

	for {
		if seen[current] {
			// Circular list detected
			return false
		}
		seen[current] = true

		if current.Cdr == typ.Nil || current.Cdr.Type() == typ.NULL {
			return true
		}

		next, ok := current.Cdr.(*typ.Pair)
		if !ok {
			return false
		}

		current = next
	}
}

func listLength(obj typ.Object) int {
	if obj == typ.Nil || obj.Type() == typ.NULL {
		return 0
	}

	pair, ok := obj.(*typ.Pair)
	if !ok {
		return -1 // Not a list
	}

	length := 0
	current := pair

	for current != nil {
		length++
		if current.Cdr == typ.Nil || current.Cdr.Type() == typ.NULL {
			break
		}

		next, ok := current.Cdr.(*typ.Pair)
		if !ok {
			return -1 // Not a proper list
		}

		current = next
	}

	return length
}

func isSymbolList(obj typ.Object) bool {
	if obj == typ.Nil || obj.Type() == typ.NULL {
		return true
	}

	pair, ok := obj.(*typ.Pair)
	if !ok {
		return false
	}

	// Check if current element is a symbol
	if _, ok := pair.Car.(*typ.Symbol); !ok {
		return false
	}

	// Recursively check remaining elements
	return isSymbolList(pair.Cdr)
}

// isFalse checks if an object is a false value
// In Scheme, only #f and empty list () are considered false
func isFalse(obj typ.Object) bool {
	// Check if it's the boolean value #f
	if boolean, ok := obj.(*typ.Boolean); ok {
		return !boolean.Value
	}

	// Check if it's an empty list
	if obj.Type() == typ.NULL {
		return true
	}

	// All other values are considered true
	return false
}
